package in.shadowfax.proswipebutton;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorGroup;
import ohos.agp.animation.AnimatorProperty;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.*;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.components.element.VectorElement;
import ohos.agp.utils.Color;
import ohos.app.Context;
import ohos.multimodalinput.event.TouchEvent;

import static in.shadowfax.proswipebutton.Constants.DEFAULT_SWIPE_DISTANCE;
import static in.shadowfax.proswipebutton.Constants.MORPH_ANIM_DURATION;
import static in.shadowfax.proswipebutton.UiUtils.*;

/**
 * Created by shadow-admin on 24/10/17.
 */
public class ProSwipeButton extends DependentLayout {

    private final Context context;
    private ShapeElement gradientDrawable;
    private DependentLayout contentContainer;
    private Text contentTv;
    private VectorComponent arrow1;
    private VectorComponent arrow2;
    private DirectionalLayout arrowHintContainer;
    private CircleProgressbar progressBar;

    private Image resultIcon;

    /*
        User configurable settings
     */
    private String btnText = "BUTTON";
    private int textColorInt;
    private int bgColorInt;
    private int arrowColorInt;
    private float btnRadius = Constants.getBTN_INIT_RADIUS(getContext());
    private float textSize = Constants.getDEFAULT_TEXT_SIZE(getContext());

    private OnSwipeListener swipeListener = null;
    private float swipeDistance = DEFAULT_SWIPE_DISTANCE;

    private int oldWidth, oldHeight;

    public ProSwipeButton(Context context) {
        this(context, null);
    }

    public ProSwipeButton(Context context, AttrSet attrs) {
        this(context, attrs, null);
    }

    public ProSwipeButton(Context context, AttrSet attrs, String defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
        setAttrs(attrs);
        init();
    }

    private void setAttrs(AttrSet attrs) {
        if (attrs == null) {
            return;
        }
        attrs.getAttr("btn_text").ifPresent(attr -> {
            String btnString = attr.getStringValue();
            if (btnString != null)
                btnText = btnString;
        });
        if (attrs.getAttr("text_color").isPresent()) {
            Color color = attrs.getAttr("text_color").get().getColorValue();
            if (color != null) {
                textColorInt = color.getValue();
            }
        } else {
            textColorInt = Color.WHITE.getValue();
        }
        if (attrs.getAttr("bg_color").isPresent()) {
            Color color = attrs.getAttr("bg_color").get().getColorValue();
            if (color != null) {
                bgColorInt = color.getValue();
            }
        } else {
            bgColorInt = context.getColor(ResourceTable.Color_proswipebtn_red);
        }

        if (attrs.getAttr("arrow_color").isPresent()) {
            Color color = attrs.getAttr("arrow_color").get().getColorValue();
            if (color != null) {
                arrowColorInt = color.getValue();
            }
        } else {
            arrowColorInt = context.getColor(ResourceTable.Color_proswipebtn_translucent_white);
        }
        if (attrs.getAttr("btn_radius").isPresent()) {
            btnRadius = attrs.getAttr("btn_radius").get().getDimensionValue();
        }
        if (attrs.getAttr("text_size").isPresent()) {
            textSize = attrs.getAttr("text_size").get().getDimensionValue();
        }
    }

    private void init() {
        Component view = LayoutScatter.getInstance(context).parse(ResourceTable.Layout_view_proswipebtn, this, true);

        contentContainer = (DependentLayout) view.findComponentById(ResourceTable.Id_relativeLayout_swipeBtn_contentContainer);
        arrowHintContainer = (DirectionalLayout) view.findComponentById(ResourceTable.Id_linearLayout_swipeBtn_hintContainer);
        contentTv = (Text) view.findComponentById(ResourceTable.Id_tv_btnText);
        arrow1 = (VectorComponent) view.findComponentById(ResourceTable.Id_iv_arrow1);
        arrow2 = (VectorComponent) view.findComponentById(ResourceTable.Id_iv_arrow2);

        tintArrowHint();
        contentTv.setText(btnText);
        contentTv.setTextColor(new Color(textColorInt));
        contentTv.setTextSize((int) textSize, Text.TextSizeType.PX);
        gradientDrawable = new ShapeElement();
        gradientDrawable.setShape(ShapeElement.RECTANGLE);
        gradientDrawable.setCornerRadius(btnRadius);
        setBackgroundColor(bgColorInt);
        updateBackground();
        setupTouchListener();

        setArrangeListener((left, top, width, height) -> {
            if (oldWidth != width || oldHeight != height) {
                oldWidth = width;
                oldHeight = height;
                onSizeChanged();
            }
            return false;
        });
    }

    private void setupTouchListener() {
        setTouchEventListener((component, touchEvent) -> {

            switch (touchEvent.getAction()) {
                case TouchEvent.PRIMARY_POINT_DOWN:
                    return true;
                case TouchEvent.POINT_MOVE:
                    float pointX = touchEvent.getPointerPosition(touchEvent.getIndex()).getX();
                    if (pointX > arrowHintContainer.getWidth() / 2f &&
                            pointX + arrowHintContainer.getWidth() / 2f < getWidth() &&
                            (pointX < arrowHintContainer.getTranslationX() + arrowHintContainer.getWidth() || arrowHintContainer.getTranslationX() != 0)) {
                        // snaps the hint to user touch, only if the touch is within hint width or if it has already been displaced
                        arrowHintContainer.setTranslationX(pointX - arrowHintContainer.getWidth() / 2f);
                    }

                    if (arrowHintContainer.getTranslationX() + arrowHintContainer.getWidth() > getWidth() &&
                            arrowHintContainer.getTranslationX() + arrowHintContainer.getWidth() / 2f < getWidth()) {
                        // allows the hint to go up to a max of btn container width
                        arrowHintContainer.setTranslationX(getWidth() - arrowHintContainer.getWidth());
                    }

                    if (pointX < arrowHintContainer.getWidth() / 2f &&
                            arrowHintContainer.getTranslationX() > 0) {
                        // allows the hint to go up to a min of btn container starting
                        arrowHintContainer.setTranslationX(0);
                    }

                    return true;

                case TouchEvent.PRIMARY_POINT_UP:
                    //Release logic here
                    if (arrowHintContainer.getTranslationX() + arrowHintContainer.getWidth() > getWidth() * swipeDistance) {
                        // swipe completed, fly the hint away!
                        performSuccessfulSwipe();
                    } else if (arrowHintContainer.getTranslationX() <= 0) {
                        // upon click without swipe
                        startFwdAnim();
                    } else {
                        // swipe not completed, pull back the hint
                        animateHintBack();
                    }
                    return true;
            }

            return false;
        });
    }

    private void performSuccessfulSwipe() {
        if (swipeListener != null)
            swipeListener.onSwipeConfirm();
        morphToCircle();
    }


    private void onSizeChanged() {
        startFwdAnim();
    }

    private void animateHintBack() {
        AnimatorValue animatorValue = new AnimatorValue();
        animatorValue.setCurveType(Animator.CurveType.ACCELERATE_DECELERATE);
        animatorValue.setDuration(200);
        float start = arrowHintContainer.getTranslationX();
        float end = 0;
        animatorValue.setValueUpdateListener((animatorValue1, v) -> arrowHintContainer.setTranslationX((float) calAnimValue(start, end, v)));
        animatorValue.start();
    }

    private void startFwdAnim() {
        if (isEnabled()) {
            AnimatorProperty animatorProperty = arrowHintContainer.createAnimatorProperty();
            animatorProperty
                    .moveFromX(0)
                    .moveToX(getWidth())
                    .setDuration(1000)
                    .setCurveType(Animator.CurveType.ACCELERATE_DECELERATE);
            animatorProperty.setStateChangedListener(new UiUtils.AnimationStateListener() {
                @Override
                public void onEnd(Animator animator) {
                    super.onEnd(animator);
                    startHintInitAnim();
                }
            });
            animatorProperty.start();
        }
    }

    /**
     * animate entry of hint from the left-most edge
     */
    private void startHintInitAnim() {
        AnimatorProperty animatorProperty = arrowHintContainer.createAnimatorProperty();
        animatorProperty.moveFromX(-arrowHintContainer.getWidth())
                .moveToX(0)
                .setDuration(500);
        animatorProperty.start();
    }

    /**
     * Just like performOnClick() in a standard button,
     * this will call the attached OnSwipeListener
     * and morph the btn to a circle
     */
    public void performOnSwipe() {
        performSuccessfulSwipe();
    }

    public void morphToCircle() {
        animateFadeHide(arrowHintContainer);
        setTouchEventListener(null);

        AnimatorValue cornerAnimation = UiUtils.buildCornerValueAnim(btnRadius, Constants.getBTN_MORPHED_RADIUS(context), gradientDrawable);

        animateFadeHide(contentTv);
        AnimatorValue widthAnimation = UiUtils.buildWidthValueAnim(getEstimatedWidth(), AttrHelper.vp2px(50, context), contentContainer);

        AnimatorValue heightAnimation = UiUtils.buildHeightValueAnim(getHeight(), AttrHelper.vp2px(50, context), contentContainer);

        if (cornerAnimation != null && widthAnimation != null && heightAnimation != null) {
            AnimatorGroup animatorGroup = new AnimatorGroup();
            animatorGroup.setDuration(MORPH_ANIM_DURATION);
            animatorGroup.runParallel(cornerAnimation, widthAnimation, heightAnimation);
            animatorGroup.start();
        }

        showProgressBar();
    }

    private void morphToRect() {
        setupTouchListener();

        AnimatorValue cornerAnimation = UiUtils.buildCornerValueAnim(Constants.getBTN_MORPHED_RADIUS(context), btnRadius, gradientDrawable);

        AnimatorValue widthAnimation = UiUtils.buildWidthValueAnim(AttrHelper.vp2px(50, context), getEstimatedWidth(), contentContainer);

        AnimatorValue heightAnimation = UiUtils.buildHeightValueAnim(AttrHelper.vp2px(50, context), getHeight(), contentContainer);

        if (cornerAnimation != null && widthAnimation != null && heightAnimation != null) {
            AnimatorGroup animatorGroup = new AnimatorGroup();
            animatorGroup.setDuration(MORPH_ANIM_DURATION);
            animatorGroup.runParallel(cornerAnimation, widthAnimation, heightAnimation);
            animatorGroup.start();
        }
    }


    private void updateBackground() {
        contentContainer.setBackground(gradientDrawable);
    }

    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
        if (!enabled) {
            gradientDrawable.setRgbColor(RgbColor.fromArgbInt(context.getColor(ResourceTable.Color_proswipebtn_disabled_grey)));
            updateBackground();
            this.setAlpha(0.5f);
        } else {
            setBackgroundColor(getBackgroundColor());
            this.setAlpha(1f);
        }
    }

    private void showProgressBar() {
        if (progressBar == null) {
            progressBar = new CircleProgressbar(context);
            progressBar.setProgressColor(Color.WHITE);
            progressBar.setBorderWidth(Constants.getProgressBorder(context));
            animateFadeHide(contentTv);
            contentContainer.addComponent(progressBar, contentContainer.getHeight(), contentContainer.getHeight());
        } else {
            animateFadeShow(progressBar);
        }
    }

    public void showResultIcon(boolean isSuccess, boolean shouldReset) {
        if (resultIcon != null && resultIcon.isComponentDisplayed()) {
            return;
        }
        animateFadeHide(progressBar);
        if (resultIcon == null) {
            resultIcon = new Image(context);
            DependentLayout.LayoutConfig icLayoutParams =
                    new DependentLayout.LayoutConfig(AttrHelper.vp2px(50, context), AttrHelper.vp2px(50, context));
            resultIcon.setLayoutConfig(icLayoutParams);
            contentContainer.addComponent(resultIcon);
        }
        resultIcon.setVisibility(HIDE);
        int icon;
        if (isSuccess)
            icon = ResourceTable.Graphic_ic_check_circle_36dp;
        else
            icon = ResourceTable.Graphic_ic_cancel_full_24dp;
        VectorElement vectorElement = new VectorElement(context, icon);
        if (!vectorElement.isAntiAlias()) {
            vectorElement.setAntiAlias(true);
        }
        resultIcon.setImageElement(vectorElement);
        animateFadeShow(resultIcon);

        if (shouldReset) {
            context.getUITaskDispatcher().delayDispatch(this::resetStatus, 1000);
        }
    }

    /**
     * reset status
     */
    public void resetStatus() {
        if (resultIcon != null && resultIcon.isComponentDisplayed()) {
            animateFadeHide(resultIcon);
        }
        if (arrowHintContainer.getVisibility() != VISIBLE) {
            morphToRect();
            arrowHintContainer.setTranslationX(0);
            animateFadeShow(arrowHintContainer);
            animateFadeShow(contentTv);
        }
    }

    public void showResultIcon(boolean isSuccess) {
        showResultIcon(isSuccess, !isSuccess);
    }

    private void tintArrowHint() {
        arrow1.setFillColor(arrowColorInt);
        arrow2.setFillColor(arrowColorInt);
    }

    public interface OnSwipeListener {
        void onSwipeConfirm();
    }

    public void setText(String text) {
        this.btnText = text;
        contentTv.setText(text);
    }

    public String getText() {
        return this.btnText;
    }

    public void setTextColor(int textColor) {
        this.textColorInt = textColor;
        contentTv.setTextColor(new Color(textColor));
    }

    public int getTextColor() {
        return this.textColorInt;
    }

    public void setBackgroundColor(int bgColor) {
        this.bgColorInt = bgColor;
        gradientDrawable.setRgbColor(RgbColor.fromArgbInt(bgColor));
        updateBackground();
    }

    public int getBackgroundColor() {
        return this.bgColorInt;
    }

    public void setCornerRadius(float cornerRadius) {
        this.btnRadius = cornerRadius;
        gradientDrawable.setCornerRadius(btnRadius);
        updateBackground();
    }

    public float getCornerRadius() {
        return this.btnRadius;
    }

    public int getArrowColorRes() {
        return this.arrowColorInt;
    }

    /**
     * Include alpha in arrowColor for transparency (ex: #33FFFFFF)
     */
    public void setArrowColor(int arrowColor) {
        this.arrowColorInt = arrowColor;
        tintArrowHint();
    }

    public void setTextSize(int textSize) {
        this.textSize = textSize;
        contentTv.setTextSize(textSize, Text.TextSizeType.PX);
    }

    public float getTextSize() {
        return this.textSize;
    }

    /**
     * How much of the button must the user swipe to trigger the OnSwipeListener successfully
     *
     * @param swipeDistance float from 0.0 to 1.0 where 1.0 means user must swipe the button fully from end to end. Default is 0.85.
     */
    public void setSwipeDistance(float swipeDistance) {
        if (swipeDistance > 1.0f) {
            swipeDistance = 1.0f;
        }
        if (swipeDistance < 0.0f) {
            swipeDistance = 0.0f;
        }
        this.swipeDistance = swipeDistance;
    }

    public float getSwipeDistance() {
        return this.swipeDistance;
    }

    public void setOnSwipeListener(OnSwipeListener customSwipeListener) {
        this.swipeListener = customSwipeListener;
    }
}
